<!DOCTYPE HTML>
<html lang="cn" class="light" dir="ltr">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>光栅设备 - 计算机图形学基础</title>


        <!-- Custom HTML head -->
        
        <meta name="description" content="Drawing on an impressive roster of experts in the field, Fundamentals of Computer Graphics, Fifth Edition offers an ideal resource for computer course curricula as well as a user-friendly personal or professional reference">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff">

        <link rel="icon" href="favicon.svg">
        <link rel="shortcut icon" href="favicon.png">
        <link rel="stylesheet" href="css/variables.css">
        <link rel="stylesheet" href="css/general.css">
        <link rel="stylesheet" href="css/chrome.css">
        <link rel="stylesheet" href="css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="fonts/fonts.css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="highlight.css">
        <link rel="stylesheet" href="tomorrow-night.css">
        <link rel="stylesheet" href="ayu-highlight.css">

        <!-- Custom theme stylesheets -->

        <!-- MathJax -->
        <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    </head>
    <body class="sidebar-visible no-js">
    <div id="body-container">
        <!-- Provide site root to javascript -->
        <script>
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script>
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script>
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('light')
            html.classList.add(theme);
            var body = document.querySelector('body');
            body.classList.remove('no-js')
            body.classList.add('js');
        </script>

        <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">

        <!-- Hide / unhide sidebar before it is displayed -->
        <script>
            var body = document.querySelector('body');
            var sidebar = null;
            var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            } else {
                sidebar = 'hidden';
            }
            sidebar_toggle.checked = sidebar === 'visible';
            body.classList.remove('sidebar-visible');
            body.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded affix "><a href="chapter_0.html">前言</a></li><li class="chapter-item expanded affix "><a href="chapter_0_1.html">致谢</a></li><li class="chapter-item expanded affix "><a href="chapter_0_2.html">作者</a></li><li class="chapter-item expanded "><a href="chapter_1.html"><strong aria-hidden="true">1.</strong> 介绍</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="chapter_1_1.html"><strong aria-hidden="true">1.1.</strong> 图形学领域</a></li><li class="chapter-item expanded "><a href="chapter_1_2.html"><strong aria-hidden="true">1.2.</strong> 主要应用</a></li><li class="chapter-item expanded "><a href="chapter_1_3.html"><strong aria-hidden="true">1.3.</strong> 图形API</a></li><li class="chapter-item expanded "><a href="chapter_1_4.html"><strong aria-hidden="true">1.4.</strong> 图形管道</a></li><li class="chapter-item expanded "><a href="chapter_1_5.html"><strong aria-hidden="true">1.5.</strong> 数值问题</a></li><li class="chapter-item expanded "><a href="chapter_1_6.html"><strong aria-hidden="true">1.6.</strong> 效率</a></li><li class="chapter-item expanded "><a href="chapter_1_7.html"><strong aria-hidden="true">1.7.</strong> 图形程序设计和编码</a></li></ol></li><li class="chapter-item expanded "><a href="chapter_2.html"><strong aria-hidden="true">2.</strong> 各种数学知识</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="chapter_2_1.html"><strong aria-hidden="true">2.1.</strong> 集合和映射</a></li><li class="chapter-item expanded "><a href="chapter_2_2.html"><strong aria-hidden="true">2.2.</strong> 二次方程求解</a></li><li class="chapter-item expanded "><a href="chapter_2_3.html"><strong aria-hidden="true">2.3.</strong> 三角函数</a></li><li class="chapter-item expanded "><a href="chapter_2_4.html"><strong aria-hidden="true">2.4.</strong> 向量</a></li><li class="chapter-item expanded "><a href="chapter_2_5.html"><strong aria-hidden="true">2.5.</strong> 曲线和曲面</a></li><li class="chapter-item expanded "><a href="chapter_2_6.html"><strong aria-hidden="true">2.6.</strong> 线性插值</a></li><li class="chapter-item expanded "><a href="chapter_2_7.html"><strong aria-hidden="true">2.7.</strong> 三角形</a></li></ol></li><li class="chapter-item expanded "><a href="chapter_3.html"><strong aria-hidden="true">3.</strong> 光栅图像</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="chapter_3_1.html" class="active"><strong aria-hidden="true">3.1.</strong> 光栅设备</a></li><li class="chapter-item expanded "><a href="chapter_3_2.html"><strong aria-hidden="true">3.2.</strong> 图像、像素和几何学</a></li><li class="chapter-item expanded "><a href="chapter_3_3.html"><strong aria-hidden="true">3.3.</strong> RGB颜色</a></li><li class="chapter-item expanded "><a href="chapter_3_4.html"><strong aria-hidden="true">3.4.</strong> 阿尔法合成</a></li><li class="chapter-item expanded "><a href="chapter_3_5.html"><strong aria-hidden="true">3.5.</strong> FAQ</a></li><li class="chapter-item expanded "><a href="chapter_3_6.html"><strong aria-hidden="true">3.6.</strong> 练习</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle">
                <div class="sidebar-resize-indicator"></div>
            </div>
        </nav>

        <!-- Track and set sidebar scroll position -->
        <script>
            var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
            sidebarScrollbox.addEventListener('click', function(e) {
                if (e.target.tagName === 'A') {
                    sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
                }
            }, { passive: true });
            var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
            sessionStorage.removeItem('sidebar-scroll');
            if (sidebarScrollTop) {
                // preserve sidebar scroll position when navigating via links within sidebar
                sidebarScrollbox.scrollTop = sidebarScrollTop;
            } else {
                // scroll sidebar to current active section when navigating via "next/previous chapter" buttons
                var activeSection = document.querySelector('#sidebar .active');
                if (activeSection) {
                    activeSection.scrollIntoView({ block: 'center' });
                }
            }
        </script>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky">
                    <div class="left-buttons">
                        <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </label>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">计算机图形学基础</h1>

                    <div class="right-buttons">
                        <a href="print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>

                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script>
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="光栅设备"><a class="header" href="#光栅设备">光栅设备</a></h1>
<p>  在讨论抽象的光栅图像之前，了解使用这些图像的一些具体设备的基本操作是很有意义的。一些熟悉的光栅设备可以被归入一个简单的层次结构中：</p>
<ul>
<li>输出
<ul>
<li>显示器
<ul>
<li>透射式显示器：液晶显示器（LCD）</li>
<li>自发光式显示器：发光二极管（LED）显示器</li>
</ul>
</li>
<li>打印
<ul>
<li>二值图像：喷墨打印机</li>
<li>连续色调（continuous-tone）图像：热升华打印机</li>
</ul>
</li>
</ul>
</li>
<li>输入
<ul>
<li>二维阵列传感器：数码相机</li>
<li>一维阵列传感器：平板扫描仪</li>
</ul>
</li>
</ul>
<h2 id="显示器"><a class="header" href="#显示器">显示器</a></h2>
<p>  目前的显示器，包括电视和数字电影放映机以及计算机显示器和投影仪，几乎普遍基于固定的像素阵列。它们可以分为自发光式显示器，它使用直接发射可控光量的像素，和透射式显示器，其中像素本身不发光，而是改变它们允许通过的光量。透射式显示器需要光源来照亮它们：在直视型显示器中，光源是阵列后面的背光;在投影仪中，光源是一盏灯，它发出的光穿过阵列后投射到屏幕上。而自发光式显示器便是其自身的光源。</p>
<p>  发光二极管（LED）显示器是自发光式显示器的一个例子。每个像素由一个或多个LED组成，这是一种基于通过电流强度决定发光亮度的半导体设备（基于有机/无机半导体）（图3.1）。</p>
<p><img src="./img/3.1.png" alt="3.1" /></p>
<p><strong>图3.1：</strong> 发光二极管（LED）显示器的运行机制。</p>
<p><img src="./img/3.2.png" alt="3.2" /></p>
<p><strong>图3.2：</strong> 组成平板显示器中单个像素的红、绿、蓝子像素。</p>
<p>  彩色显示器上的像素被细分为3个独立控制的子像素，一个红色、一个绿色、一个蓝色，每个LED都由不同材料组成从而发出不同颜色的光（图3.2）。当从一定距离外观看显示器时，眼睛无法分开每个独立的子像素，最后感知到的颜色便是红、绿、蓝三色混合成的颜色。</p>
<p>  液晶显示器（LCD）是透射式显示器的一个例子。液晶是一种材料，其分子结构使其能够旋转通过它的光的偏振，并且旋转的程度可以通过施加的电压来调节。LCD像素（图3.3）后面有一层偏振膜，因此它被偏振光照亮——让我们假设光是水平偏振的。</p>
<p><img src="./img/3.3.png" alt="3.3" /></p>
<p><strong>图3.3：</strong> 上图是一个处于开启状态的LCD显示器像素，其中液晶单元旋转光线的偏振方向，因此光线能通过前端偏振片。下图是一个处于关闭状态的LCD显示器像素，其中前端偏振片阻挡了所有通过后端偏光片的光线。图由Reinhard, Khan, Akyilz and Johnson(2008)提供。</p>
<p>  像素前的第二层偏振膜只传导垂直偏振光。如果液晶层两端施加电压至不改变偏振方向，则所有光都被阻挡且像素处于“关闭”状态（最小光强度）。如果电压被设置以让液晶层改变偏振方向至90度，则所有从像素后方进入的光都将从前端逃离，此时像素处于完全“开启”状态（具有最大光强度）。 而中等的电压会部分旋转偏振角度，这样前端偏振片会阻挡部分光线，最终产生的光强度介于最大值与最小值之间（图3.4）。就像彩色LED显示器一样，彩色LCD显示器的像素中也包含红、绿、蓝子像素，实质上是三个独立的、覆盖有红、绿、蓝三种颜色过滤器的像素。</p>
<p><img src="./img/3.4.png" alt="3.4" /></p>
<p><strong>图3.4：</strong> 液晶显示器（LCD）的运行机制。</p>
<p>  任何形式的有固定像素阵列的显示器——包括以上这些或其他技术——都有一个由阵列大小决定的基础的固定分辨率。对于显示器或图像，分辨率仅仅意味着像素阵列的尺寸：如果桌面显示器有1920*1200的分辨率，那么它总共有2304000个像素分布在1920列和1200行的像素阵列中<sup class="footnote-reference"><a href="#1">1</a></sup>。</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>显示器的分辨率有时被叫做“本地分辨率”因为许多显示器都可以通过内置转换处理其他分辨率的图片。</p>
</div>
<h2 id="打印设备"><a class="header" href="#打印设备">打印设备</a></h2>
<p>  在纸上永久记录图像的过程与在显示器上短暂地显示图片相比有许多限制。在印刷中，颜料被分布于纸上或其他打印媒介上，当光从纸上反射后便能形成想要的图案。打印机是类似于显示器的光栅设备，但许多打印机只能打印二值图像，即每个网格位置要么沉积颜料，要么不沉积，没有可能的中间值。</p>
<p><img src="./img/3.5.png" alt="3.5" /></p>
<p><strong>图3.5：</strong> 喷墨打印机的运行机制。</p>
<p>  喷墨打印机（图3.5）是一种通过扫描形成光栅图像的设备。喷墨打印头容纳了含有颜料的液体墨水，并能通过电子控制将墨水以微小墨滴的形式喷出。喷墨头在纸上横向移动，当它经过应该接受墨水网格位置时便会将墨滴喷射出；想要保持空白的位置则不会喷射墨滴。喷墨头每次扫描（sweep）之后，纸张都会略微前移，随后网格的下一行被喷射到纸上。彩色打印机使用数个打印头，每个喷头喷射一种不同的颜色的墨水，因此每个网格位置可接受任何由不同颜色的墨滴混合成的颜色。由于所有墨滴都一样，一台打印二值图像的喷墨打印机在每个网格格点上要么有墨滴要么没有墨滴，没有中间的色度（shade）。</p>
<p>  喷墨打印机没有物理像素阵列，其分辨率由墨滴的尺寸以及喷墨头每次扫描后纸张前进的距离决定。许多喷墨打印机的打印头中有多个喷嘴，允许其在一次横向移动中完成多次扫描，但最终是由纸张前移距离决定行距而非喷嘴间距<sup class="footnote-reference"><a href="#2">2</a></sup>。</p>
<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup>
<p>也存在连续喷墨打印机，它以连续的螺旋路径打印包裹在旋转的滚筒周围的纸张，而不是来回移动打印头。</p>
</div>
<p><img src="./img/3.6.png" alt="3.6" /></p>
<p><strong>图3.6：</strong> 热敏染料转印打印机的运行机制。</p>
<p>  热敏染料转印工艺是连续色调印刷工艺的一个例子，这意味着可以在每个像素上沉积不同量的染料 - 它不像喷墨打印机那样全有或全无（图3.6）。将含有彩色染料的供体色带压在纸张（或染料接收器）与包含加热组件线性阵列的打印头之间，图片的每列像素分配一个加热组件。当纸张和色带经过打印头时，加热组件通过打开和关闭状态来在需要染料的区域加热色带，使染料从色带扩散到纸张。对几种染料中的每一种重复此过程。由于较高的温度会导致更多的染料被转印，因此可以控制在每个网格位置沉积的每种染料的量，从而产生连续的颜色范围。打印头中加热组件的数量在页面横向方向上确定了固定的分辨率，但沿页面的分辨率由加热、冷却速率与纸张速度的关系决定。</p>
<p>  与显示器不同，打印机的分辨率是根据像素密度而不是像素总数来描述的。因此，如果热敏染料转印打印机的打印头上每英寸的加热元素间距为300，则其整个页面的分辨率为每英寸300个像素（ppi）。如果选择沿页面的分辨率相同，我们可以简单地说打印机的分辨率为300ppi。将点放在每英寸 1200 个网格点上的喷墨打印机被描述为分辨率为每英寸1200个点（dpi）。由于喷墨打印机是二值设备，因此至少出于两个原因，它需要更精细的网格。由于边缘是突兀的黑/白边界，因此需要非常高的分辨率来避免出现锯齿（stair-stepping/aliasing）（详见第9.3节）。打印连续色调图像时，需要高分辨率通过打印称为半色调的不同密度点图案来模拟中间色<sup class="footnote-reference"><a href="#3">3</a></sup>。</p>
<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup>
<p>术语“DPI”经常被用于表达“PPI”的含义，但“DPI”应被用于涉及二值设备的情况，而“PPI”应被用于涉及连续色调设备的情况。</p>
</div>
<h2 id="输入设备"><a class="header" href="#输入设备">输入设备</a></h2>
<p>  光栅图像不能凭空产生，并且任何不是由算法产生的图像都需要通过光栅输入设备记录（通常是相机或扫描仪）。即使是3D场景渲染而成的图像也通常使用照片作为纹理映射（详见第11章）。光栅输入设备需要对每个像素进行光记录，并且（就像输出设备一样）它们通常基于传感器阵列。</p>
<p>  数码相机就是一种二维阵列输入设备。相机中的图像传感器是一个拥有光敏像素网格的半导体设备。两种常见的阵列类型是电荷耦合元件（CCDs, charge-coupled devices）和互补金属氧化物半导体（CMOS, complimentary metal-oxide-semiconductor）图像传感器。相机的镜头将要被照相的景物的图片传感器上，然后每个像素记录落在其上的光能，最终产生构成输出图像的数字（图3.7）。就像颜色通过红、绿、蓝子像素进行显示一样，许多相机通过使用“色彩滤镜阵列”（color-filter array）或“马赛克”（mosaic）来让每个像素只能感知红光、绿光或蓝光，而图像处理软件则在称为“去马赛克”（demosaicking）的过程中填充缺失值（图3.8）。</p>
<p><img src="./img/3.7.png" alt="3.7" /></p>
<p><strong>图3.7：</strong> 数码相机的运行机制。</p>
<p><img src="./img/3.8.png" alt="3.8" /></p>
<p><strong>图3.8：</strong> 许多彩色数字相机使用类似于上图所示的“拜尔马赛克”（Bayer mosaic）的色彩滤镜阵列。每个像素记录红光、绿光或蓝光。</p>
<p>  其他相机使用3个独立阵列或阵列中的3个独立层级来在每个像素上记录独立的红、绿、蓝值，直接产生可用的彩色图片而无需更进一步地处理。相机分辨率由阵列中像素的固定数量决定，并且通常被描述为像素总数：一台阵列有3000列和2000行的相机产生分辨率3000*2000的图像，其中有六百万像素，称为“6兆像素”（MP, megapixel）相机。需要注意马赛克传感器并不记录一幅完整的图像，因此记录相同像素数但具有独立红色、绿色和蓝色记录值的相机比使用马赛克传感器的相机记录更多有关图像的信息<sup class="footnote-reference"><a href="#4">4</a></sup>。</p>
<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup>
<p>买相机的人通常使用兆（mega）来表示\(10^6\)，而不是兆字节（megabytes）代表的数量级\(2^{20}\)。</p>
</div>
<p>  平板扫描仪也记录每个像素网格的红色、绿色和蓝色值。但与热敏染料转印打印机一样，它使用一维阵列横扫正在被扫描的页面，每秒进行多次记录（图3.9）。页面的横向分辨率由阵列的尺寸确定，并且页面的纵向分辨率由记录频率和扫描头移动的速度确定。彩色扫描仪具有 \(3\times n_x\) 阵列，其中\(n_x\)是页面的横向像素数，包括三行由红色、绿色和蓝色滤镜覆盖（的子像素）。在记录三种颜色的时间之间有适当的延迟，这允许在每个网格点进行三个独立的颜色记录。与连续色调打印机一样，扫描仪的分辨率以每英寸像素数 （ppi） 为单位进行报告<sup class="footnote-reference"><a href="#5">5</a></sup>。</p>
<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup>
<p>由于许多扫描仪可以通过内置转换产生其他分辨率的图像，扫描仪的分辨率有时被称为“光学分辨率”。</p>
</div>
<p>  有了这些关于我们的图像来自哪里以及它们将去哪里的具体信息，我们现在将更抽象地讨论图像，就像我们在图形算法中使用它们一样。</p>
<p><img src="./img/3.9.png" alt="3.9" /></p>
<p><strong>图3.9：</strong> 平板扫描仪的运行机制。</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="chapter_3.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>

                            <a rel="next prefetch" href="chapter_3_2.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="chapter_3.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>

                    <a rel="next prefetch" href="chapter_3_2.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
            </nav>

        </div>

        <!-- Livereload script (if served using the cli tool) -->
        <script>
            const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
            const socket = new WebSocket(wsAddress);
            socket.onmessage = function (event) {
                if (event.data === "reload") {
                    socket.close();
                    location.reload();
                }
            };

            window.onbeforeunload = function() {
                socket.close();
            }
        </script>



        <script>
            window.playground_copyable = true;
        </script>


        <script src="elasticlunr.min.js"></script>
        <script src="mark.min.js"></script>
        <script src="searcher.js"></script>

        <script src="clipboard.min.js"></script>
        <script src="highlight.js"></script>
        <script src="book.js"></script>

        <!-- Custom JS scripts -->


    </div>
    </body>
</html>
